/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package resmgmt

import (
	reqContext "context"
	"io"
	"io/ioutil"
	"math/rand"
	"os"
	"strings"
	"time"

	"git.cloud.inspur.com/ichain/ichain-protos-go/common"
	pb "git.cloud.inspur.com/ichain/ichain-protos-go/inode"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/multi"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/retry"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/errors/status"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/filter"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/verifier"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/chconfig"
	contextImpl "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/context"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/ledger"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/resource"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/context"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/iam"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/thirdparty/ichain/protoutil"
	"github.com/pkg/errors"
)

// InstallCCRequest 安装合约请求
type InstallCCRequest struct {
	Name    string
	Path    string
	Version string
	Package *resource.CCPackage
}

// InstallCCResponse 安装合约回复
type InstallCCResponse struct {
	Target string
	Status int32
	Info   string
}

// InstantiateCCRequest 实例化合约请求
type InstantiateCCRequest struct {
	Name       string
	Path       string
	Version    string
	Lang       pb.ContractSpec_Type
	Args       [][]byte
	Policy     *common.SignaturePolicyEnvelope
	CollConfig []*common.CollectionConfig
}

// InstantiateCCResponse 实例化合约回复
type InstantiateCCResponse struct {
	TransactionID ichain.TransactionID
}

// UpgradeCCRequest 升级合约请求
type UpgradeCCRequest struct {
	Name       string
	Path       string
	Version    string
	Lang       pb.ContractSpec_Type
	Args       [][]byte
	Policy     *common.SignaturePolicyEnvelope
	CollConfig []*common.CollectionConfig
}

// UpgradeCCResponse 升级合约回复
type UpgradeCCResponse struct {
	TransactionID ichain.TransactionID
}

// SaveChainRequest 创建或更新链的请求对象
type SaveChainRequest struct {
	ChainID           string
	ChainConfig       io.Reader // 读取链配置
	ChainConfigPath   string    // 链配置路径
	SigningIdentities []iam.SigningIdentity
}

type SaveChainResponse struct {
	TransactionID ichain.TransactionID
}

type RequestOption func(ctx context.Client, opts *requestOptions) error

// requestOptions 资源客户端请求选择项
type requestOptions struct {
	Targets       []ichain.Inode // 指定inode节点
	TargetFilter  ichain.TargetFilter
	Consenser     ichain.Consenser // 指定共识节点
	Timeouts      map[ichain.TimeoutType]time.Duration
	ParentContext reqContext.Context
	Retry         retry.Opts
	Signatures    []*common.ConfigSignature // 配置签名
}

var logger = logging.NewLogger("resmgmt/client")

// Client 资源管理客户端
type Client struct {
	ctx              context.Client
	filter           ichain.TargetFilter
	localCtxProvider context.LocalProvider
}

// ClientOption describes a functional parameter for the New constructor
type ClientOption func(*Client) error

// WithDefaultTargetFilter option to configure default target filter per client
func WithDefaultTargetFilter(filter ichain.TargetFilter) ClientOption {
	return func(rmc *Client) error {
		rmc.filter = filter
		return nil
	}
}

// New 创建资源管理客户端
func New(ctxProvider context.ClientProvider, opts ...ClientOption) (*Client, error) {
	ctx, err := ctxProvider()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to create resmgmt client due to context error")
	}

	if ctx.Identifier().IAMID == "" {
		return nil, errors.New("iamID not available in user context")
	}

	resourceClient := &Client{
		ctx: ctx,
	}

	for _, opt := range opts {
		err1 := opt(resourceClient)
		if err1 != nil {
			return nil, err1
		}
	}

	if resourceClient.localCtxProvider == nil {
		resourceClient.localCtxProvider = func() (context.Local, error) {
			return contextImpl.NewLocal(
				func() (context.Client, error) {
					return resourceClient.ctx, nil
				},
			)
		}
	}

	return resourceClient, nil
}

// JoinChain 加入链
func (rc *Client) JoinChain(chainID string, options ...RequestOption) error {

	if chainID == "" {
		return errors.New("must provide chain ID")
	}

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return errors.WithMessage(err, "failed to get opts for JoinChain")
	}

	rc.resolveTimeouts(&opts)

	parentReqCtx, parentReqCancel := contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeout(opts.Timeouts[ichain.ResMgmt]), contextImpl.WithParent(opts.ParentContext))
	parentReqCtx = reqContext.WithValue(parentReqCtx, contextImpl.ReqContextTimeoutOverrides, opts.Timeouts)
	defer parentReqCancel()

	targets, err := rc.calculateTargets(opts.Targets, opts.TargetFilter)
	if err != nil {
		return errors.WithMessage(err, "failed to determine target inodes for JoinChain")
	}

	if len(targets) == 0 {
		return errors.WithStack(status.New(status.ClientStatus, status.NoInodesFound.ToInt32(), "no targets available", nil))
	}

	consenser, err := rc.requestConsenser(&opts, chainID)
	if err != nil {
		return errors.WithMessage(err, "failed to find consenser for request")
	}

	reqCtx, reqCtxCancel := contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeoutType(ichain.ConsenserResponse), contextImpl.WithParent(parentReqCtx))
	defer reqCtxCancel()

	// 生成创世区块
	genesisBlock, err := resource.GenesisBlockFromConsenser(reqCtx, chainID, consenser, resource.WithRetry(opts.Retry))
	if err != nil {
		return errors.WithMessage(err, "genesis block retrieval failed")
	}

	// 创建加入通道请求
	joinChainRequest := resource.JoinChainRequest{
		GenesisBlock: genesisBlock,
	}

	inodeReqCtx, inodeReqCtxCancel := contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeoutType(ichain.ResMgmt), contextImpl.WithParent(parentReqCtx))
	defer inodeReqCtxCancel()
	err = resource.JoinChain(inodeReqCtx, joinChainRequest, targets, resource.WithRetry(opts.Retry))
	if err != nil {
		return errors.WithMessage(err, "join chain failed")
	}

	return nil
}

func filterTargets(inodes []ichain.Inode, filter ichain.TargetFilter) []ichain.Inode {

	if filter == nil {
		return inodes
	}

	filteredInodes := []ichain.Inode{}
	for _, inode := range inodes {
		if filter.Accept(inode) {
			filteredInodes = append(filteredInodes, inode)
		}
	}

	return filteredInodes
}

func (rc *Client) resolveDefaultTargets(opts *requestOptions) ([]ichain.Inode, error) {
	if len(opts.Targets) != 0 {
		return opts.Targets, nil
	}

	localCtx, err := rc.localCtxProvider()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to create local context")
	}

	targets, err := rc.getDefaultTargets(localCtx.LocalDiscoveryService())
	if err != nil {
		return nil, err
	}
	if len(targets) == 0 {
		return nil, errors.WithMessage(err, "no local targets for InstallCC")
	}

	return targets, nil
}

func (rc *Client) getDefaultTargets(discovery ichain.DiscoveryService) ([]ichain.Inode, error) {

	inodes, err := discovery.GetInodes()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to discover inodes")
	}

	targets := filterTargets(inodes, rc.filter)

	return targets, nil

}

func (rc *Client) calculateTargets(targets []ichain.Inode, filter ichain.TargetFilter) ([]ichain.Inode, error) {

	targetFilter := filter

	if len(targets) == 0 {
		localCtx, err := rc.localCtxProvider()
		if err != nil {
			return nil, errors.WithMessage(err, "failed to create local context")
		}
		targets, err = localCtx.LocalDiscoveryService().GetInodes()
		if err != nil {
			return nil, err
		}

		if filter == nil {
			targetFilter = rc.filter
		}
	}

	if targetFilter != nil {
		targets = filterTargets(targets, targetFilter)
	}

	return targets, nil
}

// isContractInstalled 判断合约是否安装
func (rc *Client) isContractInstalled(reqCtx reqContext.Context, req InstallCCRequest, inode ichain.Inode, retryOpts retry.Opts) (bool, error) {
	// 请求inode节点的已经安装的合约
	contractQueryResponse, err := resource.QueryInstalledContracts(reqCtx, inode, resource.WithRetry(retryOpts))
	if err != nil {
		return false, err
	}

	logger.Debugf("isContractInstalled: %+v", contractQueryResponse)

	// 判断是否存在
	for _, contract := range contractQueryResponse.Contracts {
		if contract.Name == req.Name && contract.Version == req.Version && contract.Path == req.Path {
			return true, nil
		}
	}

	return false, nil
}

// InstallCC 安装合约
func (rc *Client) InstallCC(req InstallCCRequest, options ...RequestOption) ([]InstallCCResponse, error) {
	err := checkRequiredInstallCCParams(req)
	if err != nil {
		return nil, err
	}

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to get opts for InstallCC")
	}

	rc.resolveTimeouts(&opts)

	parentReqCtx, parentReqCancel := contextImpl.NewRequest(rc.ctx,
		contextImpl.WithTimeout(opts.Timeouts[ichain.ResMgmt]),
		contextImpl.WithParent(opts.ParentContext))
	parentReqCtx = reqContext.WithValue(parentReqCtx, contextImpl.ReqContextTimeoutOverrides, opts.Timeouts)
	defer parentReqCancel()

	defaultTargets, err := rc.resolveDefaultTargets(&opts)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to get default targets for InstallCC")
	}

	targets, err := rc.calculateTargets(defaultTargets, opts.TargetFilter)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to determine target inodes for install cc")
	}

	if len(targets) == 0 {
		return nil, errors.WithStack(status.New(status.ClientStatus, status.NoInodesFound.ToInt32(), "no targets available", nil))
	}

	responses, newTargets, errs := rc.adjustTargets(targets, req, opts.Retry, parentReqCtx)

	if len(newTargets) == 0 {
		return responses, errs.ToError()
	}

	reqCtx, cancel := contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeoutType(ichain.ResMgmt), contextImpl.WithParent(parentReqCtx))
	defer cancel()

	responses, err = rc.sendInstallCCRequest(req, reqCtx, newTargets, responses)

	if err != nil {
		installErrs, ok := err.(multi.Errors)
		if ok {
			errs = append(errs, installErrs)
		} else {
			errs = append(errs, err)
		}
	}

	return responses, errs.ToError()
}

func (rc *Client) sendInstallCCRequest(req InstallCCRequest, reqCtx reqContext.Context, newTargets []ichain.Inode, responses []InstallCCResponse) ([]InstallCCResponse, error) {
	icr := resource.InstallContractRequest{Name: req.Name, Path: req.Path, Version: req.Version, Package: req.Package}
	transactionProposalResponse, _, err := resource.InstallContract(reqCtx, icr, newTargets)
	if err != nil {
		return nil, errors.WithMessage(err, "installing contract failed")
	}

	for _, v := range transactionProposalResponse {
		logger.Debugf("Install contract '%s' endorser '%s' returned ProposalResponse status:%v", req.Name, v.Endorser, v.Status)

		response := InstallCCResponse{Target: v.Endorser, Status: v.Status}
		responses = append(responses, response)
	}
	return responses, nil
}

// adjustTargets 过滤一遍已经安装合约的节点
func (rc *Client) adjustTargets(targets []ichain.Inode, req InstallCCRequest, retry retry.Opts, parentReqCtx reqContext.Context) ([]InstallCCResponse, []ichain.Inode, multi.Errors) {
	errs := multi.Errors{}

	responses := make([]InstallCCResponse, 0)

	newTargets := make([]ichain.Inode, 0)
	for _, target := range targets {
		reqCtx, cancel := contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeoutType(ichain.InodeResponse), contextImpl.WithParent(parentReqCtx))
		defer cancel()

		installed, err1 := rc.isContractInstalled(reqCtx, req, target, retry)
		if err1 != nil {
			errs = append(errs, errors.Errorf("unable to verify if cc is installed on %s. Got error: %s", target.URL(), err1))
			continue
		}
		if installed {
			response := InstallCCResponse{Target: target.URL(), Info: "already installed"}
			responses = append(responses, response)
		} else {
			newTargets = append(newTargets, target)
		}
	}

	return responses, newTargets, errs

}

// checkRequiredInstallCCParams 校验安装合约参数
func checkRequiredInstallCCParams(req InstallCCRequest) error {
	if req.Name == "" || req.Version == "" || req.Path == "" || req.Package == nil {
		return errors.New("Contract name, version, path and contract package are required")
	}
	return nil
}

// InstantiateCC 实例化合约
func (rc *Client) InstantiateCC(chainID string, req InstantiateCCRequest, options ...RequestOption) (InstantiateCCResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return InstantiateCCResponse{}, errors.WithMessage(err, "failed to get opts for InstantiateCC")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.ResMgmt)
	defer cancel()

	txID, err := rc.sendCCProposal(reqCtx, InstantiateContract, chainID, req, opts)
	return InstantiateCCResponse{TransactionID: txID}, err
}

// UpgradeCC 更新合约
func (rc *Client) UpgradeCC(chainID string, req UpgradeCCRequest, options ...RequestOption) (UpgradeCCResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return UpgradeCCResponse{}, errors.WithMessage(err, "failed to get opts for UpgradeCC")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.ResMgmt)
	defer cancel()

	txID, err := rc.sendCCProposal(reqCtx, UpgradeContract, chainID, InstantiateCCRequest(req), opts)
	return UpgradeCCResponse{TransactionID: txID}, err
}

// QueryInstalledContracts inode节点请求已安装的合约
func (rc *Client) QueryInstalledContracts(options ...RequestOption) (*pb.ContractQueryResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, err
	}

	if len(opts.Targets) != 1 {
		return nil, errors.New("only one target is supported")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.InodeResponse)
	defer cancel()

	return resource.QueryInstalledContracts(reqCtx, opts.Targets[0], resource.WithRetry(opts.Retry))
}

// QueryInstantiatedContracts 查询已经实例化的合约
func (rc *Client) QueryInstantiatedContracts(chainID string, options ...RequestOption) (*pb.ContractQueryResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, err
	}

	chCtx, err := contextImpl.NewChain(
		func() (context.Client, error) {
			return rc.ctx, nil
		},
		chainID,
	)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to create chain context")
	}

	var target ichain.Inode
	if len(opts.Targets) >= 1 {
		target = opts.Targets[0]
	} else {
		// select random chain inode
		var srcpErr error
		target, srcpErr = rc.selectRandomChainInode(chCtx)
		if srcpErr != nil {
			return nil, srcpErr
		}
	}

	l, err := ledger.NewLedger(chainID)
	if err != nil {
		return nil, err
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.InodeResponse)
	defer cancel()

	// Chain service membership is required to verify signature
	chainService := chCtx.ChainService()

	membership, err := chainService.Membership()
	if err != nil {
		return nil, errors.WithMessage(err, "membership creation failed")
	}

	responses, err := l.QueryInstantiatedContracts(reqCtx, []ichain.Inode{target}, &verifier.Signature{Membership: membership})
	if err != nil {
		return nil, err
	}

	return responses[0], nil
}

// selectRandomChainInode 随机选择Inode
func (rc *Client) selectRandomChainInode(ctx context.Chain) (ichain.Inode, error) {
	discovery, err := ctx.ChainService().Discovery()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to get discovery service")
	}
	targets, err := rc.getDefaultTargets(discovery)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to get default target for query instantiated contracts")
	}

	// 过滤一遍
	targets = filterTargets(targets, filter.NewIAMFilter(ctx.Identifier().IAMID))

	if len(targets) == 0 {
		return nil, errors.Errorf("no targets in IAM [%s]", ctx.Identifier().IAMID)
	}

	randomNumber := rand.Intn(len(targets))
	return targets[randomNumber], nil
}

// QueryChains 查询所有链
func (rc *Client) QueryChains(options ...RequestOption) (*pb.ChainQueryResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, err
	}

	if len(opts.Targets) != 1 {
		return nil, errors.New("only one target is supported")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.InodeResponse)
	defer cancel()

	return resource.QueryChains(reqCtx, opts.Targets[0], resource.WithRetry(opts.Retry))

}

// getCCProposalTargets 获取合约提案的inode节点
func (rc *Client) getCCProposalTargets(chainID string, opts requestOptions) ([]ichain.Inode, error) {

	chCtx, err := contextImpl.NewChain(
		func() (context.Client, error) {
			return rc.ctx, nil
		},
		chainID,
	)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to create chain context")
	}

	discovery, err := chCtx.ChainService().Discovery()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to get discovery service")
	}

	if len(opts.Targets) == 0 {
		opts.Targets, err = rc.getDefaultTargets(discovery)
		if err != nil {
			return nil, errors.WithMessage(err, "failed to get default targets for cc proposal")
		}
	}

	targets, err := rc.calculateTargets(opts.Targets, opts.TargetFilter)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to determine target inodes for cc proposal")
	}

	if len(targets) == 0 {
		return nil, errors.WithStack(status.New(status.ClientStatus, status.NoInodesFound.ToInt32(), "no targets available", nil))
	}
	return targets, nil
}

// createDeployProposal 创建部署合约提案
func (rc *Client) createDeployProposal(tx ichain.Transactor, req InstantiateCCRequest, chainID string, ccProposalType contractProposalType) (*ichain.TransactionProposal, ichain.TransactionID, error) {
	deployProposal := contractDeployRequest(req)

	txh, err := tx.NewTxHeader()
	if err != nil {
		return nil, ichain.EmptyTransactionID, errors.WithMessage(err, "create transaction ID failed")
	}

	tp, err := createContractDeployProposal(tx, txh, ccProposalType, chainID, deployProposal)
	if err != nil {
		return nil, txh.TransactionID(), errors.WithMessage(err, "creating contract deploy transaction proposal failed")
	}
	return tp, txh.TransactionID(), nil
}

func (rc *Client) verifyTPSignature(chainService ichain.ChainService, txProposalResponse []*ichain.TransactionProposalResponse) error {
	// Membership is required to verify signature
	membership, err := chainService.Membership()
	if err != nil {
		return errors.WithMessage(err, "membership creation failed")
	}

	sv := &verifier.Signature{Membership: membership}
	for _, r := range txProposalResponse {
		if err := sv.Verify(r); err != nil {
			return errors.WithMessage(err, "Failed to verify signature")
		}
	}
	return nil
}

// sendCCProposal 发送更新或者实例化合约请求
func (rc *Client) sendCCProposal(reqCtx reqContext.Context, ccProposalType contractProposalType, chainID string, req InstantiateCCRequest, opts requestOptions) (ichain.TransactionID, error) {
	if err := checkRequiredCCProposalParams(chainID, &req); err != nil {
		return ichain.EmptyTransactionID, err
	}

	targets, err := rc.getCCProposalTargets(chainID, opts)
	if err != nil {
		return ichain.EmptyTransactionID, err
	}
	chainService, err := rc.ctx.ChainProvider().ChainService(rc.ctx, chainID)
	if err != nil {
		return ichain.EmptyTransactionID, errors.WithMessage(err, "Unable to get chain service")
	}

	transactor, err := chainService.Transactor(reqCtx)
	if err != nil {
		return ichain.EmptyTransactionID, errors.WithMessage(err, "get chain transactor failed")
	}

	// 创建合约提案
	tp, txnID, err := rc.createDeployProposal(transactor, req, chainID, ccProposalType)
	if err != nil {
		return txnID, err
	}

	// 发送提案
	txProposalResponse, err := transactor.SendTxProposal(tp, targets)
	if err != nil {
		return tp.TxnID, errors.WithMessage(err, "sending deploy transaction proposal failed")
	}

	// 验证提案
	err = rc.verifyTPSignature(chainService, txProposalResponse)
	if err != nil {
		return tp.TxnID, errors.WithMessage(err, "sending deploy transaction proposal failed to verify signature")
	}

	eventService, err := chainService.EventService()
	if err != nil {
		return tp.TxnID, errors.WithMessage(err, "unable to get event service")
	}

	return rc.sendTransactionAndCheckEvent(eventService, tp, txProposalResponse, transactor, reqCtx)

}

// sendTransactionAndCheckEvent 提交校验并且验证交易提交是否成功
func (rc *Client) sendTransactionAndCheckEvent(eventService ichain.EventService, tp *ichain.TransactionProposal, txProposalResponse []*ichain.TransactionProposalResponse,
	transac ichain.Transactor, reqCtx reqContext.Context) (ichain.TransactionID, error) {
	reg, statusNotifier, err := eventService.RegisterTxStatusEvent(string(tp.TxnID))
	if err != nil {
		return tp.TxnID, errors.WithMessage(err, "error registering for TxStatus event")
	}
	defer eventService.Unregister(reg)

	transactionRequest := ichain.TransactionRequest{
		Proposal:          tp,
		ProposalResponses: txProposalResponse,
	}
	if _, err := createAndSendTransaction(transac, transactionRequest); err != nil {
		return tp.TxnID, errors.WithMessage(err, "CreateAndSendTransaction failed")
	}

	select {
	case txStatus := <-statusNotifier:
		if txStatus.TxValidationCode == pb.TxValidationCode_VALID {
			return ichain.TransactionID(txStatus.TxID), nil
		}
		return ichain.TransactionID(txStatus.TxID), status.New(status.EventServerStatus, int32(txStatus.TxValidationCode), "instantiateOrUpgradeCC failed", nil)
	case <-reqCtx.Done():
		return tp.TxnID, errors.New("instantiateOrUpgradeCC timed out or cancelled")
	}
}

func checkRequiredCCProposalParams(chainID string, req *InstantiateCCRequest) error {

	if chainID == "" {
		return errors.New("must provide chain ID")
	}

	if req.Name == "" || req.Version == "" || req.Path == "" || req.Policy == nil {
		return errors.New("Contract name, version, path and policy are required")
	}

	if req.Lang == 0 || pb.ContractSpec_Type_name[int32(req.Lang)] == "" {
		req.Lang = pb.ContractSpec_GOLANG
	}

	return nil
}

func createAndSendTransaction(sender ichain.Sender, request ichain.TransactionRequest) (*ichain.TransactionResponse, error) {

	tx, err := sender.NewTx(request)
	if err != nil {
		return nil, errors.WithMessage(err, "CreateTransaction failed")
	}

	transactionResponse, err := sender.SendTx(tx)
	if err != nil {
		return nil, errors.WithMessage(err, "SendTransaction failed")

	}

	return transactionResponse, nil
}

// SaveChain 创建或者更新链
func (rc *Client) SaveChain(req SaveChainRequest, options ...RequestOption) (SaveChainResponse, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return SaveChainResponse{}, err
	}

	// 如果指定了配置路径，则读取配置文件
	if req.ChainConfigPath != "" {
		configReader, err := os.Open(req.ChainConfigPath)
		defer loggedClose(configReader)
		if err != nil {
			return SaveChainResponse{}, errors.Wrapf(err, "opening chain config file failed")
		}
		req.ChainConfig = configReader
	}

	// 校验请求
	err = rc.validateSaveChainRequest(req)
	if err != nil {
		return SaveChainResponse{}, err
	}

	logger.Debugf("saving chain: %s", req.ChainID)

	// 在reader中读取链配置
	chConfigBytes, err := extractChConfigTx(req.ChainConfig)
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "extracting chain config from ConfigTx failed")
	}

	// 获取一个共识节点
	consenser, err := rc.requestConsenser(&opts, req.ChainID)
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "failed to find consenser for request")
	}

	txID, err := rc.signAndSubmitChainConfigTx(
		req.ChainID,
		req.SigningIdentities,
		opts,
		chConfigBytes,
		consenser,
	)
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "create chain failed")
	}

	return SaveChainResponse{TransactionID: txID}, nil
}

// SubmitChainConfigTx 提交链配置
func (rc *Client) SubmitChainConfigTx(chainID string, configSignatures []*common.ConfigSignature, chConfigTx []byte) (SaveChainResponse, error) {

	opts, err := rc.prepareRequestOpts()
	if err != nil {
		return SaveChainResponse{}, err
	}

	consenser, err := rc.requestConsenser(&opts, chainID)
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "failed to find consenser for request")
	}
	chConfig, err := extractChConfigTx(strings.NewReader(string(chConfigTx)))
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "extracting chain config failed")
	}
	request := resource.CreateChainRequest{
		Name:       chainID,
		Consenser:  consenser,
		Config:     chConfig,
		Signatures: configSignatures,
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.ConsenserResponse)
	defer cancel()

	txID, err := resource.CreateChain(reqCtx, request, resource.WithRetry(opts.Retry))
	if err != nil {
		return SaveChainResponse{}, errors.WithMessage(err, "create chain failed")
	}
	return SaveChainResponse{TransactionID: txID}, nil
}

// signAndSubmitChainConfigTx 对链配置签名并提交给指定共识节点
func (rc *Client) signAndSubmitChainConfigTx(chainID string, signingIdentities []iam.SigningIdentity, opts requestOptions, chConfigTx []byte, consenser ichain.Consenser) (ichain.TransactionID, error) {
	var configSignatures []*common.ConfigSignature
	var err error
	// 如果指定了签名则使用指定好的签名，否则创建一个新的签名
	if opts.Signatures != nil {
		configSignatures = opts.Signatures
	} else {
		logger.Info("config signature not nil")
		configSignatures, err = rc.getConfigSignatures(signingIdentities, chConfigTx)
		if err != nil {
			return "", err
		}
	}

	request := resource.CreateChainRequest{
		Name:       chainID,
		Consenser:  consenser,
		Config:     chConfigTx,
		Signatures: configSignatures,
	}

	logger.Infof("signature %02x\n", configSignatures[0].Signature)
	reqCtx, cancel := rc.createRequestContext(opts, ichain.ConsenserResponse)
	defer cancel()

	txID, err := resource.CreateChain(reqCtx, request, resource.WithRetry(opts.Retry))
	if err != nil {
		return "", errors.WithMessage(err, "create chain failed")
	}
	return txID, nil
}

func (rc *Client) validateSaveChainRequest(req SaveChainRequest) error {

	if req.ChainID == "" || req.ChainConfig == nil {
		return errors.New("must provide chain ID and chain config")
	}
	return nil
}

func (rc *Client) getConfigSignatures(signingIdentities []iam.SigningIdentity, chConfig []byte) ([]*common.ConfigSignature, error) {
	var signers []iam.SigningIdentity

	if len(signingIdentities) > 0 {
		for _, id := range signingIdentities {
			if id != nil {
				signers = append(signers, id)
			}
		}
	} else if rc.ctx != nil {
		signers = append(signers, rc.ctx)
	} else {
		return nil, errors.New("must provide signing user")
	}

	return rc.createCfgSigFromIDs(chConfig, signers...)
}

func extractChConfigTx(chainConfigReader io.Reader) ([]byte, error) {
	if chainConfigReader == nil {
		return nil, errors.New("must provide a non empty chain config file")
	}
	configTx, err := ioutil.ReadAll(chainConfigReader)
	if err != nil {
		return nil, errors.WithMessage(err, "reading chain config file failed")
	}

	chConfig, err := protoutil.ExtractChainConfig(configTx)
	if err != nil {
		return nil, errors.WithMessage(err, "extracting chain config from ConfigTx failed")
	}

	return chConfig, nil
}

func (rc *Client) CreateConfigSignature(signer iam.SigningIdentity, chainConfigPath string) (*common.ConfigSignature, error) {
	if chainConfigPath == "" {
		return nil, errors.New("must provide a chain config path")
	}

	configReader, err := os.Open(chainConfigPath) //nolint
	if err != nil {
		return nil, errors.Wrapf(err, "opening chain config file failed")
	}
	defer loggedClose(configReader)

	return rc.CreateConfigSignatureFromReader(signer, configReader)
}

func (rc *Client) CreateConfigSignatureFromReader(signer iam.SigningIdentity, chainConfig io.Reader) (*common.ConfigSignature, error) {
	chConfig, err := extractChConfigTx(chainConfig)
	if err != nil {
		return nil, errors.WithMessage(err, "extracting chain config failed")
	}

	sigs, err := rc.createCfgSigFromIDs(chConfig, signer)
	if err != nil {
		return nil, err
	}

	if len(sigs) != 1 {
		return nil, errors.New("creating a config signature for 1 identity did not return 1 signature")
	}

	return sigs[0], nil
}

func (rc *Client) CreateConfigSignatureData(signer iam.SigningIdentity, chainConfigPath string) (signatureHeaderData resource.ConfigSignatureData, e error) {
	if chainConfigPath == "" {
		e = errors.New("must provide a chain config path")
		return
	}

	configReader, err := os.Open(chainConfigPath) //nolint
	if err != nil {
		e = errors.Wrapf(err, "opening chain config file failed")
		return
	}
	defer loggedClose(configReader)

	return rc.CreateConfigSignatureDataFromReader(signer, configReader)
}

func (rc *Client) CreateConfigSignatureDataFromReader(signer iam.SigningIdentity, chainConfig io.Reader) (signatureHeaderData resource.ConfigSignatureData, e error) {
	chConfig, err := extractChConfigTx(chainConfig)
	if err != nil {
		e = errors.WithMessage(err, "extracting chain config failed")
		return
	}

	sigCtx := contextImpl.Client{
		SigningIdentity: signer,
		Providers:       rc.ctx,
	}

	return resource.GetConfigSignatureData(&sigCtx, chConfig)
}

func (rc *Client) createCfgSigFromIDs(chConfig []byte, signers ...iam.SigningIdentity) ([]*common.ConfigSignature, error) {
	var configSignatures []*common.ConfigSignature
	for _, signer := range signers {

		sigCtx := contextImpl.Client{
			SigningIdentity: signer,
			Providers:       rc.ctx,
		}

		configSignature, err := resource.CreateConfigSignature(&sigCtx, chConfig)
		if err != nil {
			return nil, errors.WithMessage(err, "signing configuration failed")
		}
		configSignatures = append(configSignatures, configSignature)
	}

	return configSignatures, nil
}

func loggedClose(c io.Closer) {
	err := c.Close()
	if err != nil {
		logger.Warnf("closing resource failed: %s", err)
	}
}

func (rc *Client) getChainConfigService(chainID string, opts requestOptions) (*chconfig.ChainConfigService, error) {
	consenser, err := rc.requestConsenser(&opts, chainID)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to find consenser for request")
	}

	return chconfig.NewChainConfigService(chainID, chconfig.WithConsenser(consenser))
}

// QueryConfigBlockFromConsenser 查询配置区块
func (rc *Client) QueryConfigBlockFromConsenser(chainID string, options ...RequestOption) (*common.Block, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, err
	}

	chainConfigService, err := rc.getChainConfigService(chainID, opts)
	if err != nil {
		return nil, errors.WithMessage(err, "QueryConfig failed")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.ConsenserResponse)
	defer cancel()

	return chainConfigService.QueryBlock(reqCtx)

}

// QueryConfigFromConsenser 查询链配置
func (rc *Client) QueryConfigFromConsenser(chainID string, options ...RequestOption) (ichain.ChainCfg, error) {

	opts, err := rc.prepareRequestOpts(options...)
	if err != nil {
		return nil, err
	}

	chainConfigService, err := rc.getChainConfigService(chainID, opts)
	if err != nil {
		return nil, errors.WithMessage(err, "QueryConfig failed")
	}

	reqCtx, cancel := rc.createRequestContext(opts, ichain.ConsenserResponse)
	defer cancel()

	return chainConfigService.Query(reqCtx)

}

// requestConsenser 获取一个随机的共识节点，如果在opts中指定了共识节点，则不需要随机获取
func (rc *Client) requestConsenser(opts *requestOptions, chainID string) (ichain.Consenser, error) {
	if opts.Consenser != nil {
		return opts.Consenser, nil
	}

	consenserCfg, err := rc.consenserConfig(chainID)
	if err != nil {
		return nil, errors.WithMessage(err, "consenser not found")
	}

	consenser, err := rc.ctx.IChainProvider().CreateConsenserFromConfig(consenserCfg)
	if err != nil {
		return nil, errors.WithMessage(err, "failed to create consenser from config")
	}
	return consenser, nil

}

// consenserConfig 在配置中获取一个共识节点
func (rc *Client) consenserConfig(chainID string) (*ichain.ConsenserConfig, error) {
	consensers := rc.ctx.EndpointConfig().ChainConsensers(chainID)
	if len(consensers) > 0 {
		randomNumber := rand.Intn(len(consensers))
		return &consensers[randomNumber], nil
	}

	consensers = rc.ctx.EndpointConfig().ConsensersConfig()
	if len(consensers) == 0 {
		return nil, errors.New("no consensers found")
	}

	// 随机在配置中获取一个共识节点
	randomNumber := rand.Intn(len(consensers))
	return &consensers[randomNumber], nil
}

func (rc *Client) prepareRequestOpts(options ...RequestOption) (requestOptions, error) {
	opts := requestOptions{}
	for _, option := range options {
		err := option(rc.ctx, &opts)
		if err != nil {
			return opts, errors.WithMessage(err, "failed to read opts in resmgmt")
		}
	}

	if len(opts.Targets) > 0 && opts.TargetFilter != nil {
		return opts, errors.New("If targets are provided, filter cannot be provided")
	}

	return opts, nil
}

// createRequestContext 创建请求上下文
func (rc *Client) createRequestContext(opts requestOptions, defaultTimeoutType ichain.TimeoutType) (reqContext.Context, reqContext.CancelFunc) {

	rc.resolveTimeouts(&opts)

	if opts.Timeouts[defaultTimeoutType] == 0 {
		opts.Timeouts[defaultTimeoutType] = rc.ctx.EndpointConfig().Timeout(defaultTimeoutType)
	}

	return contextImpl.NewRequest(rc.ctx, contextImpl.WithTimeout(opts.Timeouts[defaultTimeoutType]), contextImpl.WithParent(opts.ParentContext))
}

// resolveTimeouts 设置超时时间
func (rc *Client) resolveTimeouts(opts *requestOptions) {

	if opts.Timeouts == nil {
		opts.Timeouts = make(map[ichain.TimeoutType]time.Duration)
	}

	if opts.Timeouts[ichain.ResMgmt] == 0 {
		opts.Timeouts[ichain.ResMgmt] = rc.ctx.EndpointConfig().Timeout(ichain.ResMgmt)
	}

	if opts.Timeouts[ichain.ConsenserResponse] == 0 {
		opts.Timeouts[ichain.ConsenserResponse] = rc.ctx.EndpointConfig().Timeout(ichain.ConsenserResponse)
	}

	if opts.Timeouts[ichain.InodeResponse] == 0 {
		opts.Timeouts[ichain.InodeResponse] = rc.ctx.EndpointConfig().Timeout(ichain.InodeResponse)
	}
}
